use std::collections::HashMap;
use std::net::{IpAddr, Ipv4Addr};
use std::sync::{Arc, Mutex};

use super::Server;

/// A struct to manage multiple servers.
pub struct ServerManager {
    /// A HashMap that stores servers indexed by their IP addresses, wrapped in `Arc<Mutex<>>` for thread safety.
    pub servers: HashMap<IpAddr, Arc<Mutex<Server>>>,
}

impl ServerManager {
    /// Creates a new instance of `ServerManager`.
    pub fn new() -> Self {
        ServerManager {
            servers: HashMap::new(),
        }
    }

    /// Adds a server to the manager.
    pub fn add_server(&mut self, server: Arc<Mutex<Server>>) {
        let ip = server.lock().expect("Failed to lock server").ip;
        self.servers.insert(ip, server);
    }

    /// Retrieves a server by its IP address.
    pub fn get_server(&self, ip: &IpAddr) -> Option<Arc<Mutex<Server>>> {
        self.servers.get(ip).cloned()
    }

    /// Adds a neighboring server based on the provided indices in the adjacency matrix.
    pub fn add_neighbor(&self, i: usize, j: usize) {
        let ip_i = IpAddr::V4(Ipv4Addr::new(127, 0, 0, i as u8));
        let ip_j = IpAddr::V4(Ipv4Addr::new(127, 0, 0, j as u8));

        if let (Some(server_i), Some(server_j)) = (self.get_server(&ip_i), self.get_server(&ip_j)) {
            let mut server_i = server_i.lock().unwrap();
            server_i.add_neighbor(&server_j);
        } else {
            eprintln!("Invalid server indices: {} or {}", i, j);
        }
    }

    /// Applies a closure to all servers managed by the `ServerManager`.
    pub fn apply_to_all_servers<F>(&self, mut f: F)
    where
        F: FnMut(&mut Server),
    {
        for server in self.servers.values() {
            let mut server_guard = server.lock().expect("Failed to lock server");
            f(&mut *server_guard);
        }
    }
}
